package com.neusoft.databus.sap.trap;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SnmptrapServer implements CommandResponder {

    /** 告警的网络流水号，唯一标识一条告警，告警关键字段 */
    public final static String _1 = "alarmId";
    /** 告警种类 */
    public final static String _2 = "type";
    /** 告警发生时间，告警关键字段 */
    public final static String _3 = "alarmTime";
    /** 设备Dn，站点的唯一标识 */
    public final static String _7 = "assetCode";
    /** 告警级别，告警关键字段 */
    public final static String _11 = "alarmLevel";
    /** 告警扩展信息，即网管当前告警界面的“定位信息”，告警关键字段 */
    public final static String _27 = "iMAPNorthboundAlarmExtendInfo";
    /** 告警发生原因，即网管当前告警界面的“名称”，告警关键字段 */
    public final static String _28 = "iMAPNorthboundAlarmProbablecause";
    public final static String alarmContent = "alarmContent";

    private MultiThreadedMessageDispatcher dispatcher;
    private Snmp snmp = null;
    private Address listenAddress;
    private ThreadPool threadPool;
    private final SnmptrapProcessor snmptrapProcessor;

    public SnmptrapServer(final String localAddress, final int port, final int threadPoolSize,
            final SnmptrapProcessor processor)
            throws Exception {
        this(localAddress, port, threadPoolSize, processor, "public");
    }

    public SnmptrapServer(final String localAddress, final int port, final int threadPoolSize,
            final SnmptrapProcessor processor, final String community)
            throws Exception {
        this.snmptrapProcessor = processor;
        this.init(localAddress, port, threadPoolSize, community);
    }

    private void init(final String localAddress, final int port, final int threadPoolSize, final String community)
            throws UnknownHostException, IOException {
        this.threadPool = ThreadPool.create("snmp-trap-server", threadPoolSize);
        this.dispatcher = new MultiThreadedMessageDispatcher(this.threadPool, new MessageDispatcherImpl());
        this.listenAddress = GenericAddress.parse("udp:" + localAddress + "/" + port);
        TransportMapping<?> transport;
        if (this.listenAddress instanceof UdpAddress) {
            transport = new DefaultUdpTransportMapping((UdpAddress) this.listenAddress);
        } else {
            transport = new DefaultTcpTransportMapping((TcpAddress) this.listenAddress);
        }
        this.snmp = new Snmp(this.dispatcher, transport);
        this.snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
        this.snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
        this.snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
        final USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
        SecurityModels.getInstance().addSecurityModel(usm);

        final CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(community));

    }

    public void start() throws Exception {
        this.snmp.listen();
        this.snmp.addCommandResponder(this);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void processPdu(final CommandResponderEvent event) {

        if (SnmptrapServer.log.isDebugEnabled()) {
            SnmptrapServer.log.debug("snmp trap server {} receive trap event: {}", Thread.currentThread().getName(),
                    event);
        }
        if (event.getPDU() == null) {
            return;
        }
        final Vector<VariableBinding> recVBs = (Vector<VariableBinding>) event.getPDU().getVariableBindings();
        if (recVBs.size() < 7) {
            SnmptrapServer.log.warn("the trap variable bindings is less than 7.");
            return;
        }

        //final SnmptrapBean snmpTrapBean = new SnmptrapBean();
        final Map<String, String> map = new HashMap<>();
        final OID anyOID = recVBs.get(0).getOid();
        map.put("iSubSystemNo", String.valueOf(anyOID.get(6)));

        recVBs.stream().forEach(vb -> {
            if (SnmpConstants.sysUpTime.equals(vb.getOid()) || SnmpConstants.snmpTrapOID.equals(vb.getOid())) {
                return;
            }
            final int lastIndexValue = vb.getOid().get(vb.getOid().size() - 2);
            String variableValue;
            if (vb.getVariable() instanceof OctetString) {
                final OctetString os = (OctetString) vb.getVariable();
                variableValue = new String(os.getValue());
            } else {
                variableValue = vb.getVariable().toString();
            }

            switch (lastIndexValue) {
            case 1:
                map.put(SnmptrapServer._1, variableValue);
                break;
            case 2:
                map.put(SnmptrapServer._2, variableValue);
                break;
            case 3:
                map.put(SnmptrapServer._3, variableValue);
                break;
            case 7:
                map.put(SnmptrapServer._7, variableValue);
                break;
            case 11:
                map.put(SnmptrapServer._11, variableValue);
                break;
            case 27:
                map.put(SnmptrapServer._27, variableValue);
                break;
            case 28:
                map.put(SnmptrapServer._28, variableValue);
                break;
            default:
                break;
            }

        });

        this.snmptrapProcessor.process(map);
    }

}
